/*
Copyright (C) BABEC. All rights reserved.
Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"strconv"

	"chainmaker.org/chainmaker/common/v2/crypto"
	"chainmaker.org/chainmaker/contract-sdk-go/v2/pb/protogo"
	"chainmaker.org/chainmaker/contract-sdk-go/v2/sandbox"
	"chainmaker.org/chainmaker/contract-sdk-go/v2/sdk"
)

type ShareDataContract struct {
}

const (
	UNAUTH = "0"
	YKC    = "1"
	XGS    = "2"
)

// 授权信息
type Auth struct {
	Account  string
	Category string
}

// Page find by page
type Page struct {
	PageSize int
	PageNum  int
}

type ShareItem struct {
	OrderId  string
	Sender   *BaseItem
	Receiver *BaseItem
}

type StateItem struct {
	StateId  string
	Sender   *BaseItem
	Receiver *BaseItem
}

type BaseItem struct {
	Name     string
	Hash     string
	CreateTM uint64
	Remark   string
	TxHash   string
}

type SetOrderDataReq struct {
	DataInfo *BaseItem
	OrderId  string
}

// InitContract install contract func
func (f *ShareDataContract) InitContract() protogo.Response {
	origin, _ := sdk.Instance.Origin()
	err := sdk.Instance.PutState("ownerinfo", "ownerkey", origin)
	if err != nil {
		return sdk.Error("ERR--InitContract fail")
	}
	return sdk.Success([]byte("Init contract success"))
}

// UpgradeContract upgrade contract func
func (f *ShareDataContract) UpgradeContract() protogo.Response {
	return sdk.Success([]byte("Upgrade contract success"))
}

/*sdk.Instance.EmitEvent("issue project", []string{string(projectInfoBytes)})
sdk.Instance.GetTxTimeStamp()
sdk.Instance.GetCreatorPk()
ShareDataContract contract for fact*/

// InvokeContract the entry func of invoke contract func
func (f *ShareDataContract) InvokeContract(method string) protogo.Response {
	switch method {
	case "authorize":
		return f.authorize(Converter1(&Auth{}).(*Auth))
	case "setOrderData":
		return f.setOrderData(Converter1(&SetOrderDataReq{}).(*SetOrderDataReq))
	case "getOrderData":
		params := sdk.Instance.GetArgs()
		orderId := string(params["orderId"])
		return f.getOrderData(orderId)
	case "getOrders":
		return f.getOrders(Converter1(&Page{}).(*Page))
	case "checkOrderExist":
		params := sdk.Instance.GetArgs()
		orderId := string(params["orderId"])
		return f.checkOrderExist(orderId)
	case "getOrderSize":
		return f.getOrderSize()
	case "setStateData":
		return f.setStateData(Converter1(&SetOrderDataReq{}).(*SetOrderDataReq))
	case "getStateData":
		params := sdk.Instance.GetArgs()
		stateId := string(params["stateId"])
		return f.getStateData(stateId)
	case "getStates":
		return f.getStates(Converter1(&Page{}).(*Page))
	case "checkStateExist":
		params := sdk.Instance.GetArgs()
		stateId := string(params["stateId"])
		return f.checkStateExist(stateId)
	case "getStateSize":
		return f.getStateSize()
	default:
		return sdk.Error("ERR--invalid method" + method)
	}
}

const (
	authinfo = "authinfo"

	mapNameOrder   = "order"
	mapNameOrderId = "orderId"
	orderkeyMaxIdx = "orderkeyMaxIdx"
	mapNameState   = "state"
	mapNameStateId = "stateId"
	statekeyMaxIdx = "statekeyMaxIdx"
)

func (f *ShareDataContract) transferOwner(account string) protogo.Response {

	if len(account) == 0 {
		return sdk.Error("ERR--param error")
	}

	//拥有人
	owner, err := sdk.Instance.GetState("ownerinfo", "ownerkey")
	if err != nil {
		return sdk.Error("ERR--fail to get ownerinfo")
	}

	sender, _ := sdk.Instance.Sender()

	//只有创建人和拥有人才能转移
	if sender == owner {
		err = sdk.Instance.PutState("ownerinfo", "ownerkey", account)
	} else {
		return sdk.Error("ERR--only owner call")
	}

	// 返回结果
	return sdk.Success([]byte("owner changed"))
}

func (f *ShareDataContract) authorize(auth *Auth) protogo.Response {

	sender, _ := sdk.Instance.Sender()
	owner, err1 := sdk.Instance.GetState("ownerinfo", "ownerkey")
	if err1 != nil {
		return sdk.Error("ERR--fail to get ownerinfo")
	}

	if sender != owner {
		return sdk.Error("ERR--only owner ")
	}

	if auth.Category != UNAUTH && auth.Category != YKC && auth.Category != XGS {
		return sdk.Error("ERR--param error")
	}

	// 存储数据
	err := sdk.Instance.PutStateByte("authinfo", auth.Account, []byte(auth.Category))
	if err != nil {
		return sdk.Error("ERR--fail to save authinfo")
	}

	// 返回结果
	return sdk.Success([]byte("authorize changed"))
}

func (f *ShareDataContract) setOrderData(req *SetOrderDataReq) protogo.Response {

	// 查询授权信息
	sender, _ := sdk.Instance.Sender()
	authresult, err := sdk.Instance.GetStateByte(authinfo, sender)

	if err != nil {
		return sdk.Error("ERR--failed to call get_state authinfo")
	}

	if string(authresult) != YKC && string(authresult) != XGS {
		return sdk.Error("ERR--not auth ")
	}

	idOrderMap, err := sdk.NewStoreMap(mapNameOrderId, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameOrderId, err))
	}

	orderMap, err := sdk.NewStoreMap(mapNameOrder, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameOrder, err))
	}

	// 初始化ShareItem对象
	shareItem := &ShareItem{}
	data, _ := orderMap.Get([]string{req.OrderId})

	dataNoExists := (data == nil || len(data) == 0)

	if len(data) == 0 {
		data = nil
	}

	err = json.Unmarshal(data, shareItem)

	if string(authresult) == YKC {
		if shareItem.Sender != nil {
			return sdk.Error("ERR--orderId already exists ")
		}
		if shareItem.Receiver != nil {
			sdk.Instance.EmitEvent("sharedata", []string{"sharedata OrderNotSync " + req.OrderId})
			//判断两个指令是否一致
			if equalToBaseItem(req.DataInfo, shareItem.Receiver) {
				sdk.Instance.EmitEvent("sharedata", []string{"OrderChecked true"})
			} else {
				sdk.Instance.EmitEvent("sharedata", []string{"OrderChecked false"})
			}
		}
		shareItem.OrderId = req.OrderId
		shareItem.Sender = req.DataInfo
		statehash, _ := sdk.Instance.GetTxId()
		shareItem.Sender.TxHash = statehash

	} else if string(authresult) == XGS {
		if shareItem.Receiver != nil {
			return sdk.Error("ERR--orderId already exists ")
		}
		if shareItem.Sender != nil {
			//判断两个指令是否一致
			if equalToBaseItem(req.DataInfo, shareItem.Sender) {
				sdk.Instance.EmitEvent("sharedata", []string{"OrderChecked true"})
			} else {
				sdk.Instance.EmitEvent("sharedata", []string{"OrderChecked false"})
			}
		} else {
			sdk.Instance.EmitEvent("sharedata", []string{"sharedata OrderNotSync " + req.OrderId})
		}
		shareItem.OrderId = req.OrderId
		shareItem.Receiver = req.DataInfo
		statehash, _ := sdk.Instance.GetTxId()
		shareItem.Receiver.TxHash = statehash
	}

	// 将ShareItem对象存储在map中
	d, _ := json.Marshal(shareItem)
	errSet := orderMap.Set([]string{req.OrderId}, d)
	if errSet != nil {
		return sdk.Error(fmt.Sprintf("ERR--%s", errSet))
	}

	if dataNoExists {
		// 设置 下标，以便分页查询直接先查询下标，再根据id查询（当然也可以直接使用切片存储）
		maxIdxStr, _ := sdk.Instance.GetStateFromKey(orderkeyMaxIdx)
		nextIdx, _ := strconv.Atoi(maxIdxStr)
		nextIdx++
		sdk.Instance.PutStateFromKey(orderkeyMaxIdx, strconv.Itoa(nextIdx))
		idOrderMap.Set([]string{"idx" + strconv.Itoa(nextIdx)}, []byte(req.OrderId))
	}

	var e []string

	orgid, _ := sdk.Instance.GetSenderOrgId()
	e = append(e, "setOrderData")
	e = append(e, orgid)
	e = append(e, string(d))
	e = append(e, string(authresult))

	sdk.Instance.EmitEvent("setsharedata", e)

	return sdk.Success([]byte("ok"))
}

func (f *ShareDataContract) setStateData(req *SetOrderDataReq) protogo.Response {

	// 查询授权信息
	sender, _ := sdk.Instance.Sender()
	authresult, err := sdk.Instance.GetStateByte("authinfo", sender)
	if err != nil {
		return sdk.Error("ERR--failed to call get_state authinfo")
	}

	if string(authresult) != YKC && string(authresult) != XGS {
		return sdk.Error("ERR--not auth ")
	}

	idStateMap, err := sdk.NewStoreMap(mapNameStateId, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameStateId, err))
	}

	stateMap, err := sdk.NewStoreMap(mapNameState, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameState, err))
	}

	// 初始化ShareItem对象
	stateItem := &StateItem{}
	data, _ := stateMap.Get([]string{req.OrderId})

	dataNoExists := (data == nil || len(data) == 0)

	if len(data) == 0 {
		data = nil
	}

	err = json.Unmarshal(data, stateItem)

	if string(authresult) == XGS {
		if stateItem.Sender != nil {
			return sdk.Error("ERR--stateId already exists ")
		}
		if stateItem.Receiver != nil {
			sdk.Instance.EmitEvent("statedata", []string{"statedata OrderNotSync " + req.OrderId})
			//判断两个指令是否一致
			if equalToBaseItem(req.DataInfo, stateItem.Receiver) {
				sdk.Instance.EmitEvent("statedata", []string{"OrderChecked true"})
			} else {
				sdk.Instance.EmitEvent("statedata", []string{"OrderChecked false"})
			}
		}
		stateItem.StateId = req.OrderId
		stateItem.Sender = req.DataInfo
		statehash, _ := sdk.Instance.GetTxId()
		stateItem.Sender.TxHash = statehash
	} else if string(authresult) == YKC {
		if stateItem.Receiver != nil {
			return sdk.Error("ERR--orderId already exists ")
		}
		if stateItem.Sender != nil {
			//判断两个指令是否一致
			if equalToBaseItem(req.DataInfo, stateItem.Sender) {
				sdk.Instance.EmitEvent("statedata", []string{"statedata true"})
			} else {
				sdk.Instance.EmitEvent("statedata", []string{"statedata false"})
			}
		} else {
			sdk.Instance.EmitEvent("statedata", []string{"statedata OrderNotSync " + req.OrderId})
		}
		stateItem.StateId = req.OrderId
		stateItem.Receiver = req.DataInfo
		statehash, _ := sdk.Instance.GetTxId()
		stateItem.Receiver.TxHash = statehash
	}

	// 将ShareItem对象存储在map中
	d, _ := json.Marshal(stateItem)
	errSet := stateMap.Set([]string{req.OrderId}, d)
	if errSet != nil {
		return sdk.Error(fmt.Sprintf("ERR--%s", errSet))
	}

	if dataNoExists {
		// 设置 下标，以便分页查询直接先查询下标，再根据id查询（当然也可以直接使用切片存储）
		maxIdxStr, _ := sdk.Instance.GetStateFromKey(statekeyMaxIdx)
		nextIdx, _ := strconv.Atoi(maxIdxStr)
		nextIdx++
		sdk.Instance.PutStateFromKey(statekeyMaxIdx, strconv.Itoa(nextIdx))
		idStateMap.Set([]string{"idx" + strconv.Itoa(nextIdx)}, []byte(req.OrderId))

	}

	var e []string
	orgid, _ := sdk.Instance.GetSenderOrgId()
	e = append(e, "setStateData")
	e = append(e, orgid)
	e = append(e, string(d))
	e = append(e, string(authresult))

	sdk.Instance.EmitEvent("setsharedata", e)

	return sdk.Success([]byte("ok"))
}

func (f *ShareDataContract) getOrderData(orderId string) protogo.Response {

	orderMap, err := sdk.NewStoreMap(mapNameOrder, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameOrder, err))
	}

	data, err2 := orderMap.Get([]string{orderId})
	if err2 != nil {
		return sdk.Error(fmt.Sprintf("ERR--%s", err2))
	}

	// 返回结果
	return sdk.Success(data)
}

func (f *ShareDataContract) getStateData(stateId string) protogo.Response {

	stateMap, err := sdk.NewStoreMap(mapNameState, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameState, err))
	}

	data, err2 := stateMap.Get([]string{stateId})
	if err2 != nil {
		return sdk.Error(fmt.Sprintf("ERR--%s", err2))
	}

	// 返回结果
	return sdk.Success(data)
}

func (f *ShareDataContract) checkOrderExist(orderId string) protogo.Response {

	orderMap, err := sdk.NewStoreMap(mapNameOrder, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameOrder, err))
	}

	data, _ := orderMap.Get([]string{orderId})

	if (data == nil) || (len(data) == 0) {
		return sdk.Success([]byte("false"))
	} else {
		// 返回结果
		return sdk.Success([]byte("true"))
	}
}

func (f *ShareDataContract) checkStateExist(stateId string) protogo.Response {

	stateMap, err := sdk.NewStoreMap(mapNameState, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameState, err))
	}

	data, _ := stateMap.Get([]string{stateId})

	if data == nil || len(data) == 0 {
		return sdk.Success([]byte("false"))
	} else {
		// 返回结果
		return sdk.Success([]byte("true"))
	}
}

func (f *ShareDataContract) getOrderSize() protogo.Response {

	maxIdxStr, _ := sdk.Instance.GetStateFromKey(orderkeyMaxIdx)
	// 返回结果
	return sdk.Success([]byte(maxIdxStr))

}

func (f *ShareDataContract) getStateSize() protogo.Response {

	maxIdxStr, _ := sdk.Instance.GetStateFromKey(statekeyMaxIdx)
	// 返回结果
	return sdk.Success([]byte(maxIdxStr))

}

func (f *ShareDataContract) getOrders(page *Page) protogo.Response {
	idOrderMap, err := sdk.NewStoreMap(mapNameOrderId, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameOrderId, err))
	}
	orderMap, err := sdk.NewStoreMap(mapNameOrder, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameOrder, err))
	}
	if page.PageSize <= 0 {
		page.PageSize = 10
	}
	if page.PageNum <= 0 {
		page.PageNum = 1
	}

	maxIdxStr, _ := sdk.Instance.GetStateFromKey(orderkeyMaxIdx)
	maxId, _ := strconv.Atoi(maxIdxStr)

	start := (page.PageNum-1)*page.PageSize + 1
	end := start + page.PageSize

	if start > maxId {
		return sdk.Error("ERR--start Index out of bounds")
	}

	if end > maxId {
		end = maxId + 1
	}

	if start == end {
		end++
	}

	shareitems := make([]*ShareItem, 0)
	for i := start; i < end; i++ {
		orderId, err := idOrderMap.Get([]string{"idx" + strconv.Itoa(i)})
		if err != nil {
			break
		}
		data, _ := orderMap.Get([]string{string(orderId)})
		// do something
		shareitem := &ShareItem{}
		err = json.Unmarshal(data, shareitem)
		if err != nil {
			return sdk.Error("ERR--" + err.Error() + " i= " + strconv.Itoa(i) + " orderId= " + string(orderId) + " data= " + string(data))
		}
		shareitems = append(shareitems, shareitem)
	}
	resp, _ := json.Marshal(shareitems)
	return sdk.Success(resp)
}

func (f *ShareDataContract) getStates(page *Page) protogo.Response {
	idStateMap, err := sdk.NewStoreMap(mapNameStateId, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameStateId, err))
	}
	stateMap, err := sdk.NewStoreMap(mapNameState, 1, crypto.HASH_TYPE_SHA256)
	if err != nil {
		return sdk.Error(fmt.Sprintf("ERR--new storeMap of %s failed, err:%s", mapNameOrder, err))
	}
	if page.PageSize <= 0 {
		page.PageSize = 10
	}
	if page.PageNum <= 0 {
		page.PageNum = 1
	}

	maxIdxStr, _ := sdk.Instance.GetStateFromKey(statekeyMaxIdx)
	maxId, _ := strconv.Atoi(maxIdxStr)

	start := (page.PageNum-1)*page.PageSize + 1
	end := start + page.PageSize

	if start > maxId {
		return sdk.Error("ERR--start Index out of bounds")
	}

	if end > maxId {
		end = maxId + 1
	}

	if start == end {
		end++
	}

	stateItems := make([]*StateItem, 0)
	for i := start; i < end; i++ {
		stateId, err := idStateMap.Get([]string{"idx" + strconv.Itoa(i)})
		if err != nil {
			break
		}
		data, _ := stateMap.Get([]string{string(stateId)})
		// do something
		stateItem := &StateItem{}
		err = json.Unmarshal(data, stateItem)
		if err != nil {
			return sdk.Error("ERR--" + err.Error())
		}
		stateItems = append(stateItems, stateItem)
	}
	resp, _ := json.Marshal(stateItems)
	return sdk.Success(resp)
}

func equalToBaseItem(item1 *BaseItem, item2 *BaseItem) bool {
	if item2.Name == item1.Name && item2.Hash == item1.Hash && item2.CreateTM == item1.CreateTM && item2.Remark == item1.Remark {
		return true
	} else {
		return false
	}
}

func Converter1(v interface{}) interface{} {
	params := sdk.Instance.GetArgs()

	// 获取参数
	data := string(params["data"])

	err := json.Unmarshal([]byte(data), v)
	if err != nil {
		sdk.Instance.Warnf("WARN--" + err.Error())
	}
	return v
}

func Converter(v interface{}) {
	params := sdk.Instance.GetArgs()

	// 获取参数
	data := string(params["data"])

	err := json.Unmarshal([]byte(data), v)
	if err != nil {
		sdk.Instance.Warnf("ERR--" + err.Error())
	}
}

func main() {
	err := sandbox.Start(new(ShareDataContract))
	if err != nil {
		log.Fatal(err)
	}
}
